package org.peterf.foxish.opengl20;

import java.nio.Buffer;

import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;

/**
 * A set of static methods for OpenGL ES 2.0 aid.
 * 
 * @author peter
 * 
 */
public class OpenGLUtil {

    /**
     * <p>
     * Buffer usage for VBO:
     * <li>{@link #DYNAMIC}: The data store contents will be modified repeatedly
     * and used many times.</li>
     * <li>{@link #STATIC}: The data store contents will be modified once and
     * used many times.</li>
     * <li>{@link #STREAM}: The data store contents will be modified once and
     * used at most a few times.</li>
     * 
     * @author peter
     * 
     */
    public enum BufferUsage {

        DYNAMIC(GLES20.GL_DYNAMIC_DRAW), STATIC(GLES20.GL_STATIC_DRAW), STREAM(
                GLES20.GL_STREAM_DRAW);

        private int usage;

        private BufferUsage(int usage) {
            this.usage = usage;
        }

        public int getUsage() {
            return usage;
        }

    }

    /**
     * Data types.
     * 
     * @author peter
     * 
     */
    public enum Type {

        FLOAT(GLES20.GL_FLOAT), SIGNED_BYTE(GLES20.GL_BYTE), SIGNED_INT(
                GLES20.GL_INT), SIGNED_SHORT(GLES20.GL_SHORT), UNSIGNED_BYTE(
                GLES20.GL_UNSIGNED_BYTE), UNSIGNED_INT(GLES20.GL_UNSIGNED_INT), UNSIGNED_SHORT(
                GLES20.GL_UNSIGNED_SHORT);

        private int type;

        private Type(int type) {
            this.type = type;
        }

        public int getType() {
            return type;
        }

    }

    /**
     * Set texture 0 to be active.
     */
    public static void activeTexture() {
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    }

    /**
     * Assign the attribute source from a buffer. This means the data will be
     * uploaded on every draw.
     * 
     * @param attribLoc
     *            Attribute location.
     * @param size
     *            Size in bytes of a single attribute.
     * @param type
     *            Attribute data type.
     * @param normalized
     *            Whether it should be normalized.
     * @param stride
     *            Size in bytes of the offset between attributes. 0 if tightly
     *            packed.
     * @param buffer
     *            The data source.
     */
    public static void assignAttrib(int attribLoc, int size, int type,
            boolean normalized, int stride, Buffer buffer) {
        GLES20.glVertexAttribPointer(attribLoc, size, type, normalized, stride,
                buffer);
        GLES20.glEnableVertexAttribArray(attribLoc);
    }

    /**
     * Assign the attribute source from the current vertex buffer object.
     * 
     * @param attribLoc
     *            Attribute location.
     * @param size
     *            The number of elements in a single attribute.
     * @param type
     *            Attribute data type.
     * @param normalized
     *            Whether it should be normalized.
     * @param stride
     *            Size in bytes of the offset between attributes. 0 if tightly
     *            packed.
     * @param offset
     *            The offset in bytes into the current vertex buffer object,
     *            which indicates the starting point.
     */
    public static void assignAttrib(int attribLoc, int size, int type,
            boolean normalized, int stride, int offset) {
        GLES20.glVertexAttribPointer(attribLoc, size, type, normalized, stride,
                offset);
        // TODO Should glEnableVertexAttribArray() be here?
        GLES20.glEnableVertexAttribArray(attribLoc);

    }

    /**
     * Assign a 4x4 matrix uniform.
     * 
     * @param loc
     *            Uniform location.
     * @param mat
     *            The float array used as the matrix.
     * @param offset
     *            The offset into the float array.
     */
    public static void assignUniformMat4(int loc, float[] mat, int offset) {
        GLES20.glUniformMatrix4fv(loc, 1, false, mat, 0);
    }

    /**
     * Bind a buffer object to the current index buffer.
     * 
     * @param name
     *            Buffer object name. Use 0 to disable the current index buffer.
     */
    public static void bindIndexBuffer(int name) {
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, name);
    }

    /**
     * Bind a texture object to the current texture.
     * 
     * @param name
     *            The texture object.
     */
    public static void bindTexture(int name) {
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, name);
    }

    /**
     * Bind a buffer object to the current vertex buffer.
     * 
     * @param name
     *            Buffer object name. Use 0 to disable the current vertex
     *            buffer.
     */
    public static void bindVertexBuffer(int name) {
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, name);
    }

    /**
     * Clear the color buffer now with a specified color.
     * 
     * @param r
     *            Red.
     * @param g
     *            Green.
     * @param b
     *            Blue.
     * @param a
     *            Alpha.
     */
    public static void clearColor(float r, float g, float b, float a) {
        GLES20.glClearColor(r, g, b, a);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

    /**
     * Compile a shader.
     * 
     * @param shaderType
     *            Can be either {@link GLES20#GL_VERTEX_SHADER} or
     *            {@link GLES20#GL_FRAGMENT_SHADER}. Remember to call
     *            {@link #deleteShader(int)} when the shader is no longer
     *            useful.
     * @param source
     *            The source code.
     * @return The shader name. 0 upon failure.
     */
    public static int compileShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader == 0) {
            return 0;
        }
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);
        int ret[] = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, ret, 0);
        if (ret[0] == 0) {
            GLES20.glDeleteShader(shader);
            return 0;
        }
        return shader;
    }

    /**
     * Create a buffer object.
     * 
     * @return The buffer object name.
     */
    public static int createBuffer() {
        int b[] = new int[1];
        GLES20.glGenBuffers(1, b, 0);
        return b[0];
    }

    /**
     * Create a texture object.
     * 
     * @return
     */
    public static int createTexture() {
        int t[] = new int[1];
        GLES20.glGenTextures(1, t, 0);
        return t[0];
    }

    /**
     * Delete a buffer object.
     * 
     * @param name
     *            The buffer object name.
     */
    public static void deleteBuffer(int name) {
        GLES20.glDeleteBuffers(1, new int[] { name }, 0);
    }

    /**
     * Delete a program.
     * 
     * @param name
     *            The program name.
     */
    public static void deleteProgram(int name) {
        GLES20.glDeleteProgram(name);
    }

    /**
     * Delete a shader.
     * 
     * @param name
     *            The shader name.
     */
    public static void deleteShader(int name) {
        GLES20.glDeleteShader(name);
    }

    /**
     * Delete a texture object.
     * 
     * @param name
     *            The texture name.
     */
    public static void deleteTexture(int name) {
        GLES20.glDeleteTextures(1, new int[] { name }, 0);
    }

    /**
     * Get an attribute location in a program.
     * 
     * @param programName
     *            The program name.
     * @param attrib
     *            The attribute name.
     * @return The attribute location.
     */
    public static int getAttribLoc(int programName, String attrib) {
        return GLES20.glGetAttribLocation(programName, attrib);
    }

    /**
     * Get a shader's info log. Whether the implementation returns the info is
     * not sure.
     * 
     * @param name
     *            The shader whose info log is being queried.
     * @return The info log.
     */
    public static String getShaderInfoLog(int name) {
        return GLES20.glGetShaderInfoLog(name);
    }

    /**
     * Get a uniform location in a program.
     * 
     * @param programName
     *            The program name.
     * @param uniform
     *            The uniform name.
     * @return The uniform location.
     */
    public static int getUniformLoc(int programName, String uniform) {
        return GLES20.glGetUniformLocation(programName, uniform);
    }

    /**
     * Link the vertex shader and the fragment shader to build a program.
     * Remember to call {@link #deleteProgram(int)} when the program is no
     * longer needed.
     * 
     * @param vertexName
     *            The vertex shader.
     * @param fragmentName
     *            The fragment shader.
     * @return The program. 0 upon failure.
     */
    public static int linkProgram(int vertexName, int fragmentName) {
        int program = GLES20.glCreateProgram();
        if (program == 0) {
            return 0;
        }
        GLES20.glAttachShader(program, vertexName);
        GLES20.glAttachShader(program, fragmentName);
        GLES20.glLinkProgram(program);
        int ret[] = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, ret, 0);
        if (ret[0] == 0) {
            GLES20.glDeleteProgram(program);
            return 0;
        }
        return program;
    }

    /**
     * Set the view port. By default, the left-bottom corner is (0, 0).
     * 
     * @param width
     *            Width of the view.
     * @param height
     *            Height of the view.
     */
    public static void setViewPort(int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    /**
     * Upload index buffer to the current index buffer object.
     * 
     * @param size
     *            Size in bytes of the buffer.
     * @param data
     *            The data to be uploaded.
     * @param usage
     *            The hint for usage.
     * @return Whether the buffer is successfully uploaded. You should fall back
     *         to the way without VBO if this fails.
     */
    public static boolean uploadIndexBuffer(int size, Buffer data,
            BufferUsage usage) {
        GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, size, data,
                usage.getUsage());
        return GLES20.glGetError() == GLES20.GL_NO_ERROR;
    }

    /**
     * Upload the bitmap automatically to the current texture.
     * 
     * @param bitmap
     *            The bitmap to be uploaded.
     * @return Whether the operation is successful.
     */
    public static boolean uploadTexture(Bitmap bitmap) {
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap, 0);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                GLES20.GL_CLAMP_TO_EDGE);
        return GLES20.glGetError() == GLES20.GL_NO_ERROR;
    }

    /**
     * Upload vertex buffer to the current vertex buffer object.
     * 
     * @param size
     *            Size in bytes of the buffer.
     * @param data
     *            The data to be uploaded.
     * @param usage
     *            The hint for usage.
     * @return Whether the buffer is successfully uploaded. You should fall back
     *         to the way without VBO if this fails.
     */
    public static boolean uploadVertexBuffer(int size, Buffer data,
            BufferUsage usage) {
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, size, data,
                usage.getUsage());
        return GLES20.glGetError() == GLES20.GL_NO_ERROR;
    }
    
    public static boolean updateVertexBuffer(int offset, int size, Buffer data) {
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, offset, size, data);
        return GLES20.glGetError() == GLES20.GL_NO_ERROR;
    }

    /**
     * Set the current program.
     * 
     * @param name
     *            The program to be used.
     */
    public static void useProgram(int name) {
        GLES20.glUseProgram(name);
    }

}
